#include "include/config.h"
#include "include/hvc.h"

int vm_num = 0;
struct vm_kernel_image_info vm_kernel_image_info_list[VM_NUM_MAX] = {{0, {0}}};

bool hvc_config_add_vm(u64 arg) {
    struct vm_add_config_arg config_arg;
    char vm_name[NAME_MAX_LEN] = {0};
    char cmd_line[1024] = {0};
    int ret, vm_id;

    if (copy_from_user(&config_arg, (void *)arg,
                       sizeof(struct vm_add_config_arg))) {
        WARNING("%s: failed to copy from user", __func__);
        return false;
    }

    if (copy_from_user(vm_name, (void *)config_arg.vm_name_addr,
                       config_arg.vm_name_length)) {
        WARNING("%s: failed to copy vm_name from user", __func__);
        return false;
    }

    if (copy_from_user(cmd_line, (void *)config_arg.cmd_line_addr,
                       config_arg.cmd_line_length)) {
        WARNING("%s: failed to copy cmd_line from user", __func__);
        return false;
    }

    config_arg.vm_name_addr = kva2ipa((u64)vm_name);
    config_arg.cmd_line_addr = kva2ipa((u64)cmd_line);

    ret = hvc_call(kva2ipa((u64)&config_arg), 0, 0, 0, 0, 0, 0,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_ADD_VM));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_ADD_VM not finish", __func__);
        return false;
    }

    vm_id = ret;

    if (copy_to_user((void *)config_arg.vm_id_addr, &vm_id, sizeof(int))) {
        WARNING("%s: copy to user failed", __func__);
        return false;
    }
    return true;
}

bool hvc_config_delete_vm(u64 arg) {
    int ret, i;

    INFO("enter %s() vm_id %lld\n", __func__, arg);
    ret = hvc_call(arg, 0, 0, 0, 0, 0, 0,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_DELETE_VM));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_DELETE_VM not finish", __func__);
        return false;
    }

    for (i = 0; i < VM_NUM_MAX; i++) {
        if (vm_kernel_image_info_list[i].vm_id == arg) {
            vm_kernel_image_info_list[i].vm_id = 0;
            memset(vm_kernel_image_info_list[i].image_name, 0,
                   sizeof(NAME_MAX_LEN));
            break;
        }
    }
    if (i == VM_NUM_MAX) {
        WARNING("failed to find VM[%lld] kernel image info in list\n", arg);
        return false;
    }
    vm_num--;
    return true;
}

bool hvc_config_vm_cpu(u64 arg) {
    int ret;
    struct vm_set_cpu_config_arg config_arg;
    if (copy_from_user(&config_arg, (void *)arg,
                       sizeof(struct vm_set_cpu_config_arg))) {
        WARNING("%s: failed to copy from user", __func__);
        return false;
    }

    ret = hvc_call(config_arg.vmid, config_arg.num, config_arg.allocate_bitmap,
                   config_arg.master, 0, 0, 0,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_CPU));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_CPU not finish", __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_memory_region(u64 arg) {
    struct vm_add_memory_region_config_arg config_arg;
    int ret;

    if (copy_from_user(&config_arg, (void *)arg,
                       sizeof(struct vm_add_memory_region_config_arg))) {
        WARNING("%s: failed to copy from user", __func__);
        return false;
    }

    ret = hvc_call(config_arg.vmid, config_arg.ipa_start, config_arg.length, 0,
                   0, 0, 0, HVC_MODE(HVC_CONFIG, HVC_CONFIG_MEMORY_REGION));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_MEMORY_REGION not finish", __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_emulated_device(u64 arg) {
    char dev_name[NAME_MAX_LENGTH] = {0};
    u64 cfg_list[CFG_MAX_NUM] = {0};
    int ret;
    struct vm_add_emulated_device_config_arg config_arg;

    if (copy_from_user(&config_arg, (void *)arg,
                       sizeof(struct vm_add_emulated_device_config_arg))) {
        WARNING("%s: failed to copy from user", __func__);
        return false;
    }
    if (copy_from_user(dev_name, (void *)config_arg.dev_name_addr,
                       config_arg.dev_name_length)) {
        WARNING("%s: failed to copy dev_name from user", __func__);
        return false;
    }
    dev_name[config_arg.dev_name_length] = '\0';
    if (copy_from_user(cfg_list, (void *)config_arg.cfg_list_addr,
                       config_arg.cfg_list_length * 8)) {
        WARNING("%s: failed to copy cfg_list from user", __func__);
        return false;
    }

    ret = hvc_call(config_arg.vmid, kva2ipa((u64)dev_name), config_arg.base_ipa,
                   config_arg.length, config_arg.irq_id, kva2ipa((u64)cfg_list),
                   config_arg.emu_type,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_EMULATED_DEVICE));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_EMULATED_DEVICE not finish", __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_passthrough_device_region(u64 arg) {
    struct vm_add_passthrough_device_region_config_arg config_arg;
    int ret;

    if (copy_from_user(
            &config_arg, (void *)arg,
            sizeof(struct vm_add_passthrough_device_region_config_arg))) {
        WARNING("%s: failed to copy passthrough device region config from user",
                __func__);
        return false;
    }

    ret = hvc_call(config_arg.vmid, config_arg.base_ipa, config_arg.base_pa,
                   config_arg.length, 0, 0, 0,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_PASSTHROUGH_DEVICE_REGION));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_PASSTHROUGH_DEVICE_REGION not finish",
                __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_passthrough_device_irqs(u64 arg) {
    struct vm_add_passthrough_device_irqs_config_arg config_arg;
    u64 irq_list[IRQ_MAX_NUM] = {0};
    int ret;

    if (copy_from_user(
            &config_arg, (void *)arg,
            sizeof(struct vm_add_passthrough_device_irqs_config_arg))) {
        WARNING("%s: failed to copy from user", __func__);
        return false;
    }

    if (copy_from_user(irq_list, (void *)config_arg.irqs_addr,
                       config_arg.irqs_length * 8)) {
        WARNING("%s: failed to copy irq_list from user", __func__);
        return false;
    }

    ret = hvc_call(config_arg.vmid, kva2ipa((u64)irq_list),
                   config_arg.irqs_length, 0, 0, 0, 0,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_PASSTHROUGH_DEVICE_IRQS));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_PASSTHROUGH_DEVICE_IRQS not finish",
                __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_passthrough_device_streams_ids(u64 arg) {
    struct vm_add_passthrough_device_streams_ids_config_arg config_arg;
    u64 streams_ids_list[CFG_MAX_NUM] = {0};
    int ret;

    if (copy_from_user(
            &config_arg, (void *)arg,
            sizeof(struct vm_add_passthrough_device_streams_ids_config_arg))) {
        WARNING("%s: failed to copy from user", __func__);
        return false;
    }

    if (copy_from_user(streams_ids_list, (void *)config_arg.streams_ids_addr,
                       config_arg.streams_ids_length * 8)) {
        WARNING("%s: failed to copy streams_ids_list from user", __func__);
        return false;
    }

    ret = hvc_call(
        config_arg.vmid, kva2ipa((u64)streams_ids_list),
        config_arg.streams_ids_length, 0, 0, 0, 0,
        HVC_MODE(HVC_CONFIG, HVC_CONFIG_PASSTHROUGH_DEVICE_STREAMS_IDS));
    if (ret < 0) {
        WARNING(
            "%s: hvc call HVC_CONFIG_PASSTHROUGH_DEVICE_STREAMS_IDS not finish",
            __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_dtb_device(u64 arg) {
    struct vm_add_dtb_device_config_arg config_arg;
    char dev_name[NAME_MAX_LENGTH] = {0};
    u64 irq_list[IRQ_MAX_NUM] = {0};
    int ret;

    if (copy_from_user(&config_arg, (void *)arg,
                       sizeof(struct vm_add_dtb_device_config_arg))) {
        WARNING("%s: failed to copy vm_add_dtb_device_config_arg from user",
                __func__);
        return false;
    }

    if (copy_from_user(dev_name, (void *)config_arg.dev_name_addr,
                       config_arg.dev_name_length)) {
        WARNING("%s: failed to copy dev_name from user", __func__);
        return false;
    }

    if (copy_from_user(irq_list, (void *)config_arg.irq_list_addr,
                       config_arg.irq_list_length * 8)) {
        WARNING("%s: failed to copy cfg_list from user", __func__);
        return false;
    }

    ret = hvc_call(config_arg.vmid, kva2ipa((u64)dev_name), config_arg.dev_type,
                   kva2ipa((u64)irq_list), config_arg.irq_list_length,
                   config_arg.addr_region_ipa, config_arg.addr_region_length,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_DTB_DEVICE));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_DTB_DEVICE not finish", __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_upload_kernel_image(u64 arg) {
    struct vm_load_kernel_img_file_arg cfg_arg;
    int ret;

    if (copy_from_user(&cfg_arg, (void *)arg,
                       sizeof(struct vm_load_kernel_img_file_arg))) {
        WARNING("%s: failed to copy vm_load_kernel_img_file_arg from user",
                __func__);
        return false;
    }
    ret = hvc_call(cfg_arg.vmid, cfg_arg.img_size, cfg_arg.cache_ipa,
                   cfg_arg.load_offset, cfg_arg.load_size, 0, 0,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_UPLOAD_KERNEL_IMAGE));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_UPLOAD_KERNEL_IMAGE not finish",
                __func__);
        return false;
    }
    return true;
}

bool hvc_config_vm_upload_device_tree(u64 arg) {
    struct vm_load_kernel_img_file_arg cfg_arg;
    int ret;

    if (copy_from_user(&cfg_arg, (void *)arg,
                       sizeof(struct vm_load_kernel_img_file_arg))) {
        WARNING("%s: failed to copy device tree from user",
                __func__);
        return false;
    }

    ret = hvc_call(cfg_arg.vmid, cfg_arg.img_size, cfg_arg.cache_ipa,
                   cfg_arg.load_offset, cfg_arg.load_size, 0, 0,
                   HVC_MODE(HVC_CONFIG, HVC_CONFIG_UPLOAD_DEVICE_TREE));

    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_UPLOAD_DEVICE_TREE failed",
                __func__);
        return false;
    }

    return true;
}

bool hvc_config_vm_memory_color_budget(u64 arg) {
    struct vm_memory_color_budget_config_arg cfg_arg;
    if (copy_from_user(&cfg_arg, (void *)arg, sizeof(cfg_arg))) {
        WARNING(
            "%s: failed to copy vm_memory_color_budget_config_arg from user",
            __func__);
        return false;
    }
    u64 colors[MAX_COLOR_NUM];
    if (copy_from_user(colors, (void *)cfg_arg.color_array_addr,
                       sizeof(colors[0]) * cfg_arg.color_num)) {
        WARNING(
            "%s: failed to copy vm_memory_color_budget_config_arg from user",
            __func__);
        return false;
    }
    int ret = hvc_call(cfg_arg.vmid, cfg_arg.color_num, kva2ipa((u64)colors),
                       cfg_arg.budget, 0, 0, 0,
                       HVC_MODE(HVC_CONFIG, HVC_CONFIG_MEMORY_COLOR_BUDGET));
    if (ret < 0) {
        WARNING("%s: hvc call HVC_CONFIG_MEMORY_COLOR_BUDGET not finish",
                __func__);
        return false;
    }
    return true;
}
